package io.gitee.hejiang;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.servlet.ServletContainer;
import org.h2.server.web.WebServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;

import io.gitee.hejiang.jersey.JerseyResourceConfig;
import io.swagger.inflector.config.Configuration;

@org.springframework.context.annotation.Configuration
public class SwaggerSqlJerseyConfiguration {
	private static final Logger LOGGER = LoggerFactory.getLogger(SwaggerSqlApplication.class);

	@Value("${spring.jersey.inflector-path}")
	private String inflectorPath;

	@Value("${spring.jersey.application-path}")
	private String applicationPath;

	@Value("${spring.jersey.config.server.provider.classnames}")
	private String providerClassnames;

	/**
	 * Configuration bean for JerseyResourceConfig
	 * 
	 * 从 ./config/inflector.yaml 文件读取配置信息
	 */
	@Bean
	Configuration configuration(ApplicationContext applicationContext) throws Exception {
		Configuration configuration = Configuration.read(inflectorPath);
		configuration.setControllerFactory((cls, operation) -> applicationContext.getBean(cls));
		configuration.setRootPath(applicationPath);
		return configuration;
	}

	/**
	 * JerseyResourceConfig bean for Jersey web application
	 */
	@Bean
	JerseyResourceConfig swaggerSqlResourceConfig(ApplicationContext applicationContext, Configuration config) {
		JerseyResourceConfig resourceConfig = new JerseyResourceConfig(applicationContext, config);

		// 加载通过 Java 类实现的 Jersey 资源和提供者
		// 配置在 spring.jersey.config.server.provider.classnames 中
		// 包括支持在线配置的 io.gitee.hejiang.jersey.SwaggerYamlProvider 类
		resourceConfig.property(ServerProperties.PROVIDER_CLASSNAMES, providerClassnames);

		return resourceConfig;
	}

	/**
	 * Since we're using both Actuator and Jersey, we need to use Springs <a href=
	 * "http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cors.html#_filter_based_cors_support">Filter
	 * based CORS support</a>
	 *
	 * @return corsFilter
	 */
	@Bean
	public FilterRegistrationBean<CorsFilter> corsFilter() {
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		CorsConfiguration config = new CorsConfiguration();
		config.setAllowCredentials(true);
		config.addAllowedOrigin("*");
		config.addAllowedHeader("*");
		config.addAllowedMethod("*");
		source.registerCorsConfiguration("/**", config);
		FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<CorsFilter>(new CorsFilter(source));
		bean.setOrder(0);
		return bean;
	}

	/**
	 * StatViewServlet bean for druid connection pool
	 * 
	 * 允许通过 /druid/* 路径监视 druid 连接池
	 */
	@Bean
	public ServletRegistrationBean<StatViewServlet> statViewServlet() {
		ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(),
				"/druid/*");
		bean.setLoadOnStartup(1);

		return bean;
	}

	/**
	 * WebStatFilter bean for druid connection pool
	 * 
	 * 配置 Web 和 Druid 数据源之间的管理关联监控统计
	 */
	@Bean
	public FilterRegistrationBean<Filter> webStatFilter() {
		FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
		bean.setFilter(new WebStatFilter());
		Map<String, String> map = new HashMap<>();
		map.put("exclusions", "*.js,*.css,/druid/*");

		// 拦截所有请求
		bean.setUrlPatterns(Arrays.asList("/*"));
		bean.setInitParameters(map);

		return bean;
	}

	/**
	 * WebServlet bean for h2
	 * 
	 * 允许通过 /h2/* 路径访问内置 H2 数据库
	 */
	@Bean
	public ServletRegistrationBean<WebServlet> webServlet() {
		ServletRegistrationBean<WebServlet> bean = new ServletRegistrationBean<>(new WebServlet(), "/h2/*");
		bean.setLoadOnStartup(1);

		return bean;
	}

	// 文件改变监视器
	FileAlterationMonitor _monitor;

	/**
	 * SpringBoot 加载完毕后，启动文件改变监视
	 */
	@Bean
	public CommandLineRunner commandLineRunner(ApplicationContext applicationContext,
			ServletRegistrationBean<ServletContainer> bean, Configuration config) {
		return args -> {
			File file = new File(config.getSwaggerUrl());
			File dir = file.getParentFile();
			IOFileFilter filter = FileFilterUtils.nameFileFilter(file.getName());
			FileAlterationObserver observer = new FileAlterationObserver(dir, filter);
			observer.addListener(new FileAlterationListenerAdaptor() {
				public void onFileChange(File file) {
					ServletContainer container = bean.getServlet();
					if (container.getServletConfig() != null) {
						LOGGER.warn("{} changed. Let's reload it...", file.getName());

						// 如果调用 swaggerSqlResourceConfig(), 报错如下
						// java.lang.IllegalStateException: The resource
						// configuration is not modifiable in this context.
						JerseyResourceConfig resourceConfig = new JerseyResourceConfig(applicationContext, config);

						resourceConfig.property(ServerProperties.PROVIDER_CLASSNAMES, providerClassnames);

						container.reload(resourceConfig);
					}
				}
			});

			// 启动文件改变监视
			_monitor = new FileAlterationMonitor();
			_monitor.addObserver(observer);

			try {
				_monitor.start();
			} catch (Exception e) {
				LOGGER.error("Failed to reload " + file.getName(), e);
			}
		};
	}
}
